...loading
2024-11-02
이번 포스팅은 자바스크립트를 공부하면 누구나 한 번쯤은 들어보는 클로저에 대해서 알아본다. 함수형 프로그램의 패러다임을 이해하기 위해서는 클로저에 대한 이유가 필수적이다. 특히 리액트의 함수형 컴포넌트의 기술이 클로저와 관련이 깊기에 이를 이해하는 것은 중요하다.
MDN 정의에 따른 클로저란 '함수와 함수가 선언된 어휘적 환경(Lexical Scope)의 조합'이다. 난해한 설명이지만, 해당 정의에서 주요한 개념인 Lexical Scope를 살펴보면 이해하기 편해진다.
우선적으로 Scope란 변수의 유효 범위를 의미한다. 그리고 어휘적 환경을 뜻하는 Lexical Scope란 변수가 코드 내 어디에서 선언되었는지에 따라 결정되는 변수의 유효 범위를 의미한다. 자바스크립트의 변수는 이러한 Lexical Scope를 갖는다. 다음 코드 예시를 살펴보자.
function groot(){ const x = "I am " function innerGroot(){ const y = "Groot" console.log(x+y) } innerGroot() // "I am Groot" }
변수 x의 유효 범위를 살펴보자. 이는 변수 x가 선언된 공간인 함수 groot() 내부로 결정되는 것을 알 수 있다. 그렇다면 변수 y는 어떠한가. 변수 y의 유효범위 또한 자신인 선언된 공간인 함수 innerGroot() 내부로 범위가 결정된다. 따라서 변수 y는 innerGroot() 외부에서 접근할 수 없다. 이처럼 자바스크립트 변수의 유효범위는 자신이 선언된 공간으로 정의되는 Lexical Scope를 갖게 된다.
다시 클로저로 돌아가보자. 클로저란 '함수와 Lexical Scope의 조합'이라 정의된다. 변수의 유효 범위에 관한 Lexical Scope에 대해 알게 되었으니, 이러한 유효 범위와 함수의 조합인 클로저가 특정 작업에 활용될 수 있다 점을 짐작할 수 있다. 클로저가 활용된 아래의 코드를 확인해보자.
function counter() { let x = 0; function innerCounter() { x++; console.log(x); } return innerCounter; } const increase = counter(); increase(); // 1 increase(); // 2 increase(); // 3 increase(); // 4 }
위의 코드를 보면 특이하게도 counter 함수 호출이 종료된 시점에서 이미 변수 x의 생명 주기도 끝나는게 맞다. 하지만 변수 x는 계속 기억되며 increase 연산이 진행되는 것을 알 수 있다. 이는 자바스크립트의 가비지컬렉터가 참조 가능성이 있는 변수가 있다면 이를 수집하지 않기 때문에 발생하는 현상이다. 함수 counter와 innerCounter의 클로저로 의해 변수 x의 참조가능성이 발생한다. 따라서 변수 x의 생명주기가 종료되었어야함에도, 계속 활용될 수 있는 것이다. 그리고 이러한 작업에서 클로저가 활용된다.
클로저는 정보은닉에도 활용될 수 있다. 아래의 코드는 앞선 예시를 조금 변형한 것이다.
let x = 0; function increase() { x++ console.log(x); } const increase = counter(); increase(); // 1 increase(); // 2 increase(); // 3 increase(); // 4 }
해당 코드는 x를 전역변수로 설정하였다. 기능 자체는 똑같다 볼 수 있다. 하지만 전역으로 선언된 변수 x는 어디서나 쉽게 수정될 수 있다. 즉 어떠한 범위에서도 접근할 수 있기 때문에 보호받지 못한다는 것이다. 예를 들어 window.x를 통해 변수를 접근한다면 누구나 값을 조작할 수 있게 된다. 이러한 정보은닉의 문제는 클로저를 활용하여 변수의 유효 범위를 좁힘으로써 간단하게 해결할 수 있다.
앞선 클로저의 활용과 예시를 살펴보면, 상태 관리와 연관이 있다는 점을 짐작할 수 있을 것이다. 클로저의 원리를 사용하는 대표적인 훅이 useState다.
function Component(){ const [count, setCount] = useState(0); function handleClick(){ setCount((prev) => prev+1) } }
친숙한 리액트의 코드다. useState의 호출은 이미 끝났지만, count는 계속하여 최신값으로 유지된다. 이는 클로저를 활용한 사례로 count를 보호함과 동시에 상태의 변동을 활성화시킨 것이라 볼 수 있다.
이상 자바스크립트의 클로저에 대해 알아보았다. 클로저란 Lexical Scope로 인해 발생하는 현상을 의미하며, 클로저를 잘 활용한다면 특정 변수를 보호하면서도 변화를 관리할 수 있게 된다. 그리고 이러한 특성을 활용한 것이 리액트의 대표적인 함수형 컴포넌트 useState이다. 함수형 프로그래밍에서 이러한 클로저를 잘 활용하는 것은 중요하지만, 이는 소프트웨어의 성능에도 영향을 미칠 수 있다. 호출 이후 생명주기가 종료되어야할 변수가 메모리에 계속하여 기억되기 때문이다. 따라서 클로저를 잘 이해하고 목적에 맞게 활용하는 것이 중요하다.
Comments